package com.javaspeak.concurrency.lock.custom.reentrant;

/**
 * If you look at aquireLock and releaseLock you may be thinking how can one
 * thread get into the synchronized block of the releaseLock method when the
 * other thread is waiting on the lock object in the synchronized block of the
 * aquireLock method?
 * <p>
 * The answer is that when a thread waits on a lock object it is not blocking
 * entry by other threads into other synchronized blocks synchronized on the
 * same monitor lock object. What actually happens is that the lock temporarily
 * goes into suspended mode until another thread notifies the lock object
 * or the wait times out.  The suspended thread will then wake up and carry on
 * executing any instructions after the wait statement. When it does this
 * all other threads will be locked out of  entering synchronized blocks on the
 * same monitor object.
 * <p>
 * The logic below is that the lockCount is incremented in the acquire method
 * if the thread is the same thread as when it was incremented from 0 to 1.
 * <p>
 * If however the lockCount has not been been reduced to 0 and the current
 * thread is not the owner then the thread entering aquireLock will sleep to
 * a maximum of its timeout or until it gets notified.  It may be woken from
 * its sleep earlier than the timeout if the owner (another thread) calls
 * releaseLock enough times that the lockCount drops to zero.
 * <p>
 * The usage of ReEntrantLock is great when a method which has a lock in it
 * calls another method which also has a lock in it. If the lock was not
 * reentrant then the thread would instead hang. It would hang because the
 * thread that entered method A would be blocked on progressing past the
 * lock on the first line of method B as the releaseLock() method from the
 * first lock had not been called yet.
 * B.
 * <p>
 * method A(){
 *
 *    lock.acquireLock( timeOutInMilliseconds ) ){
 *
 *    try {
 *       doSomething();
 *       methodB();
 *       doSomethingElse();
 *    }
 *    finally {
 *       lock.releaseLock();
 *    }
 * }
 *
 * method B(){
 *
 *    lock.acquireLock( timeOutInMilliseconds ) ){
 *
 *    try {
 *       doMoreOfSomething();
 *    }
 *    finally {
 *       lock.releaseLock();
 *    }
 * }
 *
 * What you may have noticed from this method A and method B example is that
 * synchronized methods must also be reentrant for else a synchronized method
 * would not be able to call another synchronized method.  With this reentrant
 * lock example above you can see we are effectively emulating a synchronized
 * method calling another synchronized method.
 * <p>
 * If you understand this example you should be able to dream up concurrent
 * structures which only lock certain areas of the structure or alternatively
 * only lock for write but not for read.  Variations of this reentrant lock
 * object would be used in these concurrent structures.
 * <p>
 * An interesting follow on from this example could be a CAS version of this
 * reentrant lock.
 *
 * @author John Dickerson
 */
public class ReEntrantLock {

    private Object lockObject = new Object();
    private Thread owner;
    private int lockCount;


    public boolean acquireLock( int maxWait ) throws InterruptedException {

        Thread currentThread = Thread.currentThread();

        synchronized ( lockObject ) {

            if ( owner == currentThread ) {

                lockCount++;
                return true;
            }
            else {

                long waitedSoFar = 0L;

                while ( owner != null ) {

                    long timeToWait = maxWait - waitedSoFar;

                    if ( timeToWait <= 0 ){

                        return false;
                    }

                    long timeZero = System.currentTimeMillis();

                    lockObject.wait( timeToWait );

                    if ( owner != null ) {

                        waitedSoFar =
                            waitedSoFar + System.currentTimeMillis() - timeZero;
                    }
                }

                owner = currentThread;
                lockCount = 1;
                return true;
            }
        }
    }


    public void releaseLock() {

        Thread currentThread = Thread.currentThread();

        synchronized ( lockObject ) {

            if ( owner == currentThread ) {

                lockCount = lockCount - 1;

                if ( lockCount == 0 ) {

                    owner = null;
                    lockObject.notify();
                }

                return;
            } else {

                throw new IllegalStateException();
            }
        }
    }
}
